package com.spring.nio.service.file.handler;

import com.alibaba.fastjson.JSON;
import com.spring.nio.business.file.service.FileService;
import com.spring.nio.business.user.dto.UserDTO;
import com.spring.nio.business.user.param.UserQueryParam;
import com.spring.nio.business.user.service.UserService;
import com.spring.nio.common.BasicConstant;
import com.spring.nio.core.channel.context.ChannelContext;
import com.spring.nio.core.channel.context.SimpleChannelContext;
import com.spring.nio.core.channel.context.SocketChannelContext;
import com.spring.nio.core.channel.handler.AbstractChannelHandler;
import com.spring.nio.core.configuration.NioServerBootstrap;
import com.spring.nio.core.event.listener.event.constant.ExceptionEventTypeEnum;
import com.spring.nio.core.event.listener.event.model.ExceptionEventSource;
import com.spring.nio.core.event.model.PacketData;
import com.spring.nio.core.exception.ExceptionManager;
import com.spring.nio.service.file.frame.FileMessageFrame;
import com.spring.nio.service.file.model.FileTransportRequest;
import com.spring.nio.service.file.model.structure.FileTransportTaskDTO;
import com.spring.nio.service.protocol.response.file.FileTransportResponse;
import com.spring.nio.util.BasicUtil;
import com.spring.nio.util.LocalTime;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.*;

/**
 * 文件在线传输实时流处理，将文件发起端用户的文件流发送给文件接收端用户中
 * @author spring
 * */

@Slf4j
public class FileReceiveHandler extends AbstractChannelHandler {
    private static UserService userService = null;
    private static FileService fileService = null;
    static {
        ClassPathXmlApplicationContext classPathXmlApplicationContext = NioServerBootstrap.getConfig().getClassPathXmlApplicationContext();
        userService = classPathXmlApplicationContext.getBean(UserService.class);
        fileService = classPathXmlApplicationContext.getBean(FileService.class);
    }

    @Override
    public void handler(Object o, ChannelContext channelContext) {
        PacketData packetData = (PacketData) o;
        packetData.getSocketChannelContext().getRealList().forEach(obj -> {
            try {
                sendFileOnlineTransportStream((FileMessageFrame) obj, packetData.getSocketChannelContext(), channelContext);
            } catch (Exception e) {
                // 异常事件(ExceptionEvent) 事件源类型为异常事件源类型(EXCEPTION_EVENT_SOURCE)  事件源数据(NioReadEventExceptionSource)
                ExceptionEventSource exceptionEventSource = new ExceptionEventSource();
                exceptionEventSource.setSocketChannelContext(packetData.getSocketChannelContext());
                exceptionEventSource.setModule("FileReceiveHandler --> method [handler] --> 文件帧在线传输接收处理异常");
                exceptionEventSource.setException(e);
                exceptionEventSource.setExceptionEventTypeEnum(ExceptionEventTypeEnum.FILE_PARSE_HANDLER_IO_EXCEPTION);
                exceptionEventSource.setIsSend(Boolean.TRUE);
                ExceptionManager.handleException(exceptionEventSource);
            }
        });
        // 终止当前handler为最后一个handler
        ((SimpleChannelContext) channelContext).setNeedStop(Boolean.TRUE);
    }

    /**
     * 文件在线传输实时流
     * @param fileMessageFrame
     * @param socketChannelContext
     * @param channelContext
     * */
    private void sendFileOnlineTransportStream(FileMessageFrame fileMessageFrame, SocketChannelContext socketChannelContext, ChannelContext channelContext) throws IOException {
        FileTransportRequest fileTransportRequest = JSON.parseObject(fileMessageFrame.getData(), FileTransportRequest.class);
        fileMessageFrame.setFileTransport(fileTransportRequest);

        // 传输文件tag
        String fileTag = fileTransportRequest.getTag();

        // 获取发起用户和接收用户
        UserQueryParam userQueryParam = new UserQueryParam();
        userQueryParam.setUserId(fileTransportRequest.getLaunchUserId());
        UserDTO launchUserDto = userService.getOnlineUserById(userQueryParam);
        userQueryParam.setUserId(fileTransportRequest.getReceiveUserId());
        UserDTO receiveUserDto = userService.getOnlineUserById(userQueryParam);

        // 校验发起端和接收端是否存在相同的文件传送任务
        FileTransportTaskDTO launchTaskDto = launchUserDto.getUploadFileMap().get(fileTag);
        FileTransportTaskDTO receiveTaskDto = receiveUserDto.getDownloadFileMap().get(fileTag);
        if(Objects.isNull(launchTaskDto) || Objects.isNull(receiveTaskDto)) {
            // 流发送过程失败，通知发送端用户和接收端用户关闭文件通道以及对应各自本地的FileChannel
            log.info("[ " + LocalTime.formatDate(LocalDateTime.now()) + " ] FileReceiveHandler | --> 文件 = [{}], tag = [{}] 在线传输过程未查询到发送端或接收端相同任务, 将关闭通道资源", fileTransportRequest.getFileName(), fileTransportRequest.getTag());
            throw new RuntimeException("系统内部错误: 未查询到在线发送端或接收端匹配的传输任务");
        }

        // 开始处理文件流
        this.onlineTransportStream(launchTaskDto, receiveTaskDto, fileTransportRequest, channelContext);
    }

    /**
     * 处理文件在线传输实时流
     * @param launchTaskDto
     * @param receiveTaskDto
     * @param fileTransportRequest
     * @param channelContext
     * */
    private void onlineTransportStream(FileTransportTaskDTO launchTaskDto, FileTransportTaskDTO receiveTaskDto, FileTransportRequest fileTransportRequest, ChannelContext channelContext) {
        List<Map<String, Object>> list = (List<Map<String, Object>>) ((SimpleChannelContext) channelContext).getObj();
        if(CollectionUtils.isEmpty(list)) {
            return;
        }

        // 按照帧序号排序,升序排序，此处的顺序必须为1~...,且序号必须连续相差1，否则文件流顺序写入错误，终止写入
        if(!this.sortStreamList(list)) {
            // 流发送过程失败，通知发送端用户和接收端用户关闭文件通道以及对应各自本地的FileChannel
            log.info("[ " + LocalTime.formatDate(LocalDateTime.now()) + " ] FileReceiveHandler | --> 文件 [{}] 在流传输过程出现流索引差值不连续(相差结果不为1)，已终止文件流发送", fileTransportRequest.getFileName());
            return;
        }

        list.stream().forEach(map -> {
            this.sendFileBytes(map.get(BasicConstant.ORIGIN_FILE_STREAM_DATA), launchTaskDto, receiveTaskDto, fileTransportRequest);
        });

        // 发送完成后清空文件流list数据,如果不清空下次发送回重复进行发送
        list.clear();
    }

    /**
     * 循环发送文件流数据
     * @param obj
     * @param launchTaskDto
     * @param receiveTaskDto
     * @param fileTransportRequest
     *         // 4B -> 当前帧的总长度；
     *         // 1B -> 文件是否读取完[0:未读取完, 1:读取完],
     *         // 4B -> 包序号
     *         // 4B -> 文件字节流长度
     */
    private void sendFileBytes(Object obj, FileTransportTaskDTO launchTaskDto, FileTransportTaskDTO receiveTaskDto, FileTransportRequest fileTransportRequest) {
        int originStreamDataIndex = 4 + 1 + 4 + 4;
        // 发送缓冲区
        ByteBuffer byteBuffer = ByteBuffer.wrap((byte[]) obj);

        // 设置发送端用户任务和接收端任务已接收和发送的包序号及流大小
        launchTaskDto.setPacketIndex(launchTaskDto.getPacketIndex() + 1);launchTaskDto.setSumSendBytes(launchTaskDto.getSumSendBytes() + byteBuffer.capacity());
        receiveTaskDto.setPacketIndex(receiveTaskDto.getPacketIndex() + 1);receiveTaskDto.setSumSendBytes(receiveTaskDto.getSumSendBytes() + byteBuffer.capacity());

        // 待发送的字节数组
        byte[] sendBytes = new byte[originStreamDataIndex + byteBuffer.capacity()];
        // 封装文件发送流
        this.encapsulationStreamData(receiveTaskDto, receiveTaskDto.getFileLength(), byteBuffer, sendBytes);
        // 包装文件流
        FileTransportResponse fileTransportResponse = new FileTransportResponse();
        fileTransportResponse.setCode("200");
        fileTransportResponse.setFrameType(FileMessageFrame.FrameType.DATA_TRANSPORT.getBit());
        fileTransportResponse.setStreamData(sendBytes);
        fileTransportResponse.setTime(Timestamp.valueOf(LocalDateTime.now()));
        // 通过当前socketChannelContext的发送队列执行发送
        receiveTaskDto.getSocketChannelContext().getBlockingQueue().add(fileTransportResponse);
        log.info("[ " + LocalTime.formatDate(LocalDateTime.now()) + " ] FileReceiveHandler | --> 文件 = [{}] 接收和发送第 [{}] 个包数据入队成功", fileTransportRequest.getFileName(), receiveTaskDto.getPacketIndex());
    }

    /**
     * 封装文件发送流
     * @param receiveTaskDto 当前文件传输任务
     * @param fileSize   当前文件大小
     * @param byteBuffer 文件文件字节数据
     * @param sendBytes  待发送的字节数组
     */
    private void encapsulationStreamData(FileTransportTaskDTO receiveTaskDto, long fileSize, ByteBuffer byteBuffer, byte[] sendBytes) {

        // 1、当前帧总长度 4B (0~3)
        byte[] frameSumLength = BasicUtil.intToBytes(sendBytes.length);
        System.arraycopy(frameSumLength, 0, sendBytes, 0, frameSumLength.length);

        // 2、是否是结束帧 1B (4)
        sendBytes[4] = (receiveTaskDto.getSumSendBytes() == fileSize)?(byte)1:(byte)0;

        // 3、包序号 4B (5~8)
        byte[] packetIndexBytes = BasicUtil.intToBytes(receiveTaskDto.getPacketIndex());
        System.arraycopy(packetIndexBytes, 0, sendBytes, 5, packetIndexBytes.length);

        // 4、文件流数据
        while(byteBuffer.hasRemaining()) {
            byte[] fileStreamBytes = new byte[byteBuffer.remaining()];
            byteBuffer.get(fileStreamBytes);

            // 5、文件字节流长度 4B (9 ~ 12)
            byte[] fileStreamByteLength = BasicUtil.intToBytes(fileStreamBytes.length);
            System.arraycopy(fileStreamByteLength, 0, sendBytes, 9, fileStreamByteLength.length);

            // 6、拷贝文件流数据
            System.arraycopy(fileStreamBytes, 0, sendBytes, 13, fileStreamBytes.length);
        }

        byteBuffer.clear();
    }

    /**
     * 按照帧序号排序,升序排序，此处的顺序必须为1~...
     * @param list
     */
    private Boolean sortStreamList(List<Map<String, Object>> list) {
        if(list.size() == 1) {
            return Boolean.TRUE;
        }

        // 先自增排序
        //list.stream().sorted(Comparator.comparing(EmployeePayrollProfileDTO::getEffectiveStartDate).reversed())
        Collections.sort(list, new Comparator<Map<String, Object>>() {
            @Override
            public int compare(Map<String, Object> m1, Map<String, Object> m2) {
                Integer index1 = ((FileMessageFrame) m1.get(BasicConstant.FILE_FRAME)).getFrameIndex();
                Integer index2 = ((FileMessageFrame) m2.get(BasicConstant.FILE_FRAME)).getFrameIndex();
                return index1.compareTo(index2);
            }
        });

        // 在判断元素间的差值是否为1
        for(int i = 0; i < (list.size() - 1); i++) {
            Integer index1 = ((FileMessageFrame) list.get(i).get(BasicConstant.FILE_FRAME)).getFrameIndex();
            Integer index2 = ((FileMessageFrame) list.get(i + 1).get(BasicConstant.FILE_FRAME)).getFrameIndex();
            if(!((index2 - index1) == 1)) {
                System.out.println("差值不为1，发送失败, 当前list中的帧序号为: " + index1 + "~" + index2);
                return Boolean.FALSE;
            }
        }

        return Boolean.TRUE;
    }

}
